/*
 * Copyright 2009 BioTeam Inc
 * 
 * Licensed under License GNU LESSER GENERAL PUBLIC LICENSE version 3.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.gnu.org/copyleft/lesser.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package net.bioteam.appweb.submit;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

import net.bioteam.appweb.model.Ctrl;
import net.bioteam.appweb.model.Paragraph;
import net.bioteam.appweb.model.Parameter;
import net.bioteam.appweb.model.ParameterType;
import net.bioteam.appweb.model.Program;
import net.bioteam.common.utils.Utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class DefaultCommandLineGenerator implements CommandGenerator
{
	public String getName()
	{
		return "DEFAULT";
	}
	public static Log logger = LogFactory.getLog(DefaultCommandLineGenerator.class);
	

	private String toShortFileName(UnfinishedJob job, String value)
	{
		if (value == null)
			return value;
		if (value.startsWith(job.getWorkdir()))
		{

			if (value.endsWith(".zip"))
			{
				// TODO find a good location for this code.
				File workdir = new File(job.getWorkdir());

				try
				{
					Utils.unzipDir(new BufferedInputStream(new FileInputStream(value)), workdir, true);
				} catch (FileNotFoundException e)
				{
					// ignore.
				} catch (IOException e)
				{
					logger.warn("fail in unzip file " + e.getMessage());
				}
			}

			return value.substring(job.getWorkdir().length() + 1);

		} else
			return value;
	}

	public String generateScript(ProgramValueBean bean, UnfinishedJob job) throws EvaluationException, CtrlException, IOException
	{
		Program program = bean.getProgram();
		String language = null;

		for (Parameter parameter : program.getParametersMap().values())
		{
			if (parameter.getFormat() != null)
			{
				language = parameter.getFormat().getLanguage();

				break;
			}
		}
		
		Map<String, String> cmdlines = new HashMap<String, String>();
		ScriptEngineManager manager = new ScriptEngineManager();
		ScriptEngine engine = manager.getEngineByName(language);

		if (engine != null)
		{
			ScriptContext newContext = new SimpleScriptContext();
			Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE);

			for (Map.Entry<String, String> entry : bean.getValueMap().entrySet())
			{
				// NOTE: some parameters are internal parameters, it does not
				// have as corresponding parameter
				engineScope.put(entry.getKey(), toShortFileName(job, entry.getValue()));

			}
			for (Parameter p : program.getAllParameters())
			{
				Object obj=engineScope.get(p.getName());
				
				
				if (obj==null)
				{
					// for javascript the object is null type of (xxx)='object'
					// if we do not set it to null, typeof is 'undefined'.
					engineScope.put(p.getName(), null);
				} else
				{
					//for beanshell, we have to pass correct numberic type for == and != operator to work.
					engineScope.put(p.getName(), p.getValueAsObject(obj.toString()));
				}
			}
			engineScope.put("_program", program);

			for (Parameter parameter : program.getParameters())
			{
				evaluateParameter(engine, parameter, newContext, cmdlines);
			}
		
		} else
		{
			throw new EvaluationException(language + " is not supported");
		}
		List<Parameter> sortedParameters = sortParametersByGroup(program.getAllParameters());
		StringBuilder buffer = new StringBuilder();
		boolean cmdused = false;

		if (program.getCommandParameter() != null)
		{
			cmdused = true;
		}

		Map<String, Writer> paramfiles = new HashMap<String, Writer>();
		try
		{
			for (Parameter parameter : sortedParameters)
			{
				if (!cmdused && (parameter.getGroup() >= 0))
				{
					cmdused = true;
					buffer.append(program.getCommand());
					buffer.append(" ");
				}

				String cmdlinePortion = cmdlines.get(parameter.getName());

				if (parameter.getParamfile() != null && parameter.getParamfile().length() > 0)
				{
					Writer out = paramfiles.get(parameter.getParamfile());
					if (out == null)
					{
						String file = job.getWorkdir() + File.separator + parameter.getParamfile();
						out = new BufferedWriter(new FileWriter(file));
						paramfiles.put(parameter.getParamfile(), out);
					}
					out.write(cmdlinePortion);
					// out.write("\n");
				} else
				{
					if (cmdlinePortion != null && cmdlinePortion.length() > 0)
					{
						buffer.append(cmdlinePortion + " ");

					}
				}
			}
		} finally
		{
			for (Writer out : paramfiles.values())
			{
				try
				{
					out.close();
				} catch (IOException e1)
				{
					// ignore.
				}
			}
		}

		return buffer.toString().trim();
	}
	 public boolean postProcessMPI()
	 {
		 return true;
	 }

	/*
	 * Sort the list by group parameters is not changed during the sort. a new
	 * list is constructed.
	 */
	protected List<Parameter> sortParametersByGroup(Collection<Parameter> parameters)
	{
		Comparator<Parameter> comparator = new Comparator<Parameter>()
		{
			public int compare(Parameter o1, Parameter o2)
			{
				int leftgroup = o1.getGroup();
				int rightgroup = o2.getGroup();

				if (leftgroup < rightgroup)
				{
					return -1;
				} else if (leftgroup == rightgroup)
				{
					return 0;
				} else
				{
					return 1;
				}
			}
		};

		List<Parameter> newlist = new ArrayList<Parameter>(parameters.size());
		newlist.addAll(parameters);
		Collections.sort(newlist, comparator);

		return newlist;
	}

	// boolean representation. 1=true, 0=false
	// used by javascript
	protected void evaluateParameter(ScriptEngine engine, Parameter parameter, ScriptContext context, Map<String, String> cmdlines) throws EvaluationException, CtrlException
	{
		Object value = null;
		Object vdef = null;
		if (!(parameter instanceof Paragraph))
		{
			Bindings engineScope = context.getBindings(ScriptContext.ENGINE_SCOPE);

			vdef=parameter.getDefaultValueAsObject();
			if (vdef==null && parameter.getType()==ParameterType.BooleanType)
			{
				vdef=0;
			}
			
			engineScope.put("vdef", vdef);

			// setup the value variable.
			if (engineScope.get(parameter.getName()) == null)
			{
				value=vdef;
			} else
			{
				value = engineScope.get(parameter.getName());
			}
			engineScope.put("value", value);
			if (parameter.getFormat() == null)
			{
				return;
			}

			engineScope.put("_parameter", parameter);

			if (parameter.getPrecond() != null)
			{
				try
				{
					Boolean b=null;
					Object obj= engine.eval(parameter.getPrecond().getCode(), context);
					if (obj==null)
					{
						//hack for my own perl script engine implementation.
						b=false;
					} else if (obj instanceof String)
					{
						//hack for my own perl script engine implementation.
						if (obj.toString().length()==0)
							b=false;
						else if (obj.toString().equals("0"))
							b=false;
						else
							b=true;
					} else
					{
						b= (Boolean)obj;
					}
					
					 

					if (!b)
					{
						// this command is not considered.
						return;
					}
				} catch (ScriptException e)
				{
					EvaluationException ee = new EvaluationException("error when evaluate precond fragnment |" + parameter.getPrecond().getCode() + "| for parameter " + parameter.getName()
							+ " in program " + parameter.getProgram().getName() + ". Error is " + e.getMessage(), e);
					ee.setParameterName(parameter.getName());
					throw ee;
				}
			}

			if (!parameter.getCtrls().isEmpty() && value != null)
			{
				for (Ctrl ctrl : parameter.getCtrls())
				{
					try
					{

						Boolean b=null;
						Object obj= engine.eval(ctrl.getCode(), context);
						if (obj instanceof String)
						{
							//hack for my own perl script engine implementation.
							if (obj.toString().length()>0)
								b=true;
							else
								b=false;
						} else
						{
							b= (Boolean)obj;
						}
						logger.info(ctrl.getCode() + " evaluation result is " + b);
						if (!b)
						{
							throw new CtrlException(ctrl.getMessage(), parameter);
						}
					} catch (ScriptException e)
					{
						EvaluationException ee = new EvaluationException("error when evaluate code fragnment |" + ctrl.getCode() + "| for parameter " + parameter.getName() + " in program "
								+ parameter.getProgram().getName() + ". Error is " + e.getMessage(), e);
						ee.setParameterName(parameter.getName());
						throw ee;
					}
				}
			}

			String code = parameter.getFormat().getCode();

			if (code == null)
			{
				return;
			}

			String cmdlinePortion = null;

			try
			{

				cmdlinePortion = (String) engine.eval(code, context);
				
//				 System.out.println("name="+parameter.getName());
//				 System.out.println("value=|"+engineScope.get("value")+"|");
//				 System.out.println("vdef=|"+engineScope.get("vdef")+"|");
//				 System.out.println("code=|"+code+"|");
//				 System.out.println("cmd=|"+cmdlinePortion+"|\n");
				 
			} catch (ScriptException e)
			{
				EvaluationException ee = new EvaluationException("error when evaluate code fragnment |" + code + "| for parameter " + parameter.getName() + " in program "
						+ parameter.getProgram().getName() + ". Error is " + e.getMessage(), e);
				ee.setParameterName(parameter.getName());
				throw ee;
			}

			cmdlines.put(parameter.getName(), cmdlinePortion);
		} else
		{
			for (Parameter parameterIn : ((Paragraph) parameter).getParameters())
			{
				evaluateParameter(engine, parameterIn, context, cmdlines);
			}
		}
	}

}
